/*!
 * Snoop - jQuery JavaScript Library Plug-in
 * http://code.google.com/p/jquery-snoop/
 *
 * Copyright (c) 2010 Lucas Nascimento Arruda
 * Licensed under the MIT license.
 * http://en.wikipedia.org/wiki/MIT_License
 *
 * Date: (Wed, 22 Apr 2010)
 *
 * @requires jQuery version 1.3+
 * @author Lucas Arruda <lucasarruda>live.com
 * @extends jQuery
 * @thanks Manoel Carvalho <manoelhc>gmail.com
 */

// jQuery Snoop JSON Object
var jQuerySnoop = {
	// Pool for hidden fields and their values and procedures
  pool : [],
  // 300 miliseconds of default interval between each check
  interval     : 300,
  thread       : null,
                 
  init : function(element, procedure) {
    if (element.length > 0) {
      this.heap(element, procedure);
      // Start the listener if it is stopped
      if (this.thread == null) {
        this.listen();
      }
    } 
  },
  
  /*
   * Implementation of heap()
   * Queues the events related to the hidden fields in a pool.
   * @param object (Object) : The selector's object
   * @param procedure (Function) : The procedure to be executed
   */   
  heap : function (element, procedure) {   
    // Empty holes in the pool can be reused with this flag that
    // check if there's a hole in the pool and if it was filled
    var holeFilled = false;
    // If the pool is not empty and a hole is found we fill it up.
    if (this.pool.length != 0) { 
      jQuery.each(this.pool, function(i,o) { 
        if (o == null) {
          jQuerySnoop.pool[i] = { selector  : element.selector,
                                  value     : element.val(),
                                  procedure : procedure,
                                  enable    : true
                                };
          holeFilled = true;
          return false;
        }			
      });
    }
    // No holes filled means the pool is empty/full so we add a new position
    if (!holeFilled) {
      this.pool.push({ selector  : element.selector, 
                       value     : element.val(),
                       procedure : procedure,
                       enable    : true
                    });
    }
  },  

  /*
   * Implementation of check()
   * Checks if the hidden field still exists and executes the related procedure
   * if the value has been changed during the last check.
   */
  check : function () { 
    // Flag used to stop the thread if the pool is empty
    var emptyPool = true;
    // Go through each position checking their stored and actual values
    jQuery.each(jQuerySnoop.pool, function(i,o) {
      if (o != null) {
        var element = jQuery(o.selector);
        if (element.length > 0) {
          if (o.value != element.val()) {
            o.value = element.val();
            if (o.enable == true && !element.is(':focused')) {
              o.procedure();
            }
          }
          if (emptyPool) {
            // The pool is not empty because at least one element was found in the DOM
            emptyPool = false;
          }
        }
        else {
          // If the element was not found delete it from the pool
          jQuerySnoop.pool[i] = null;
        }
      }
    });
    // Once the pool is empty stops thread and re-start 
    // the pool to ensure better performance on site.
    if (emptyPool) {
      clearInterval(jQuerySnoop.thread);
      jQuerySnoop.thread = null;
      jQuerySnoop.pool = [];
    }
  },
  
  /*
   * Implementation of setOption()
   * @param key (String) : The name of the option to be set up
   * @param value : The value of the followed option
   */
  setOption : function (key, value) { 
    switch (key) {
      // Sets the time to wait between each check thread.
      case 'interval':
        if (typeof(value) == 'number') {
          this.interval = value;
        }
        break;
      default:
        break;
    }
  },
  
  /*
   * Implementation of doAction()
   * @param object (Object) : The selector's object that the action will be fired for.
   * @param action (String) : The action to be executed.
   */
  doAction : function (object, action) { 
    jQuery.each(this.hiddenPool, function(i,o) {
      if (o != null) {
        if (object.selector == o.selector) {
          switch (action) {
            // Disable the listening
            case 'disable':
              jQuerySnoop.hiddenPool[i].enable = false;
              break;
            // Enable the listening
            case 'enable':
              jQuerySnoop.hiddenPool[i].enable = true;
              break;
            // Destroy the listening
            case 'destroy':
              jQuerySnoop.hiddenPool[i] = null;
              break;
            default:
              break;
          }
        }
      } 
    });
  },

  /*
   * Implementation of listen()
   * Creates the listener thread.
   */
  listen : function() {
    this.thread = window.setInterval(jQuerySnoop.check, this.interval);
  }
};
// End of Snoop JSON Object

// Verify if jQuery is loaded
if (typeof(jQuery) != 'function') {
  alert('jQuerySnoop: jQuery is not properly loaded.');
} 
else {
  var ver = jQuery.fn.jquery.split('.');
  // Verify if jQuery's version is newer than 1.3
  if (ver[0] < 1 || ver[1] < 3) {
    alert("jQuerySnoop: jQuery incompatible version.\nPlease use jQuery 1.3+");
  }
  else {
    // Create a jQuery function
    jQuery.fn.snoop = function(procedure) {
      // The param submitted by the user needs to be a function
      if (typeof(procedure) == 'function') {
        jQuerySnoop.init(this, procedure);
      }
      else {
        // Option exclusive to this selector
        if (typeof(procedure) == 'string') {
          jQuerySnoop.doAction(this, procedure);
        }
      }
    };
    // User is able to specify some options and fire some actions using snoop function without selector
    jQuery.extend({snoop : function(key, value) {
        if (typeof(key) == 'string') {
          jQuerySnoop.setOption(key, value);
        }
      }
    });
    // Declared a new selector ':focused'
    // Return true if the the element has focus
    jQuery.extend(jQuery.expr[":"], {
      focused : function(a) {
        var active = document.activeElement;
        return active == a;
      }
    });
    // onValueChange alias
    jQuery.fn.onValueChange = function(procedure) {
      jQuery(this.selector).snoop(procedure);
    };
  }
};